module chan1

import vcp.mlog
import vcp.rtcom
import vcp.futex
import vcp.iohook

const vnil = voidptr(0)

pub fn pre_main_init(yielder &rtcom.Yielder, resumer &rtcom.Resumer, allocer voidptr) {
    C.printf("chan1 premain init\n")
    ylder = yielder
    rsmer = resumer
    alcer = allocer
}

pub fn post_main_deinit() {
}

__global (
    ylder = &rtcom.Yielder(0)
    alcer = voidptr(0)
    rsmer = &rtcom.Resumer(0)
)

fn init() {

}

//////////////////
// unsafe channel, v not support generic enough
struct Chan1Impl {
    mut:
    len int // alias qcount
    cap int // alias datasize
    elemsize int
    elemtype string
    // elems []voidptr
    // elems array
    elems voidptr = 0

    //
    closed u32 // atomic
    sendx int
    recvx int
    recvq []&Fiber // fiber link list
    sendq []&Fiber // fiber link list

    mu &futex.Mutex = futex.newMutex()
}

// just Fiber header fields
pub struct Fiber {
    grid int // coroutine id
    mut:
    mcid int // machine id

    elem voidptr // channel recv/send var addr
    fromgr &Fiber = 0
    channel &Chan1Impl = 0 // sending channel
    releasetime i64
    param voidptr
    isselect bool
}

struct Waitq {
    mut:
    objs []voidptr
}

pub struct Chan1 {
    mut:
    ci &Chan1Impl = 0
}

// not encourage usage
fn C.__new_array() array
fn C.array_set() int
fn C.array_get() voidptr

pub fn new<T>(cap int) Chan1 {
    tv := T{}
    mut ci := &Chan1Impl{}
    ci.cap = if cap < 0 { 0 } else {cap}
    ci.elemsize = int(sizeof(tv))
    ci.elemtype = T.name
    if ci.cap > 0 {
        // ci.elems = []voidptr{len: ci.cap}
        // ci.elems = C.__new_array(ci.cap, ci.cap, ci.elemsize)
        ci.elems = malloc(ci.cap*ci.elemsize)
    }
    return Chan1{ci}
}
pub fn (ch Chan1) len() int { return ch.ci.len }
pub fn (ch Chan1) cap() int { return ch.ci.cap }
pub fn (ch Chan1) elemsize() int { return ch.ci.elemsize }

// ep = elem pointer
fn (ch Chan1) send0(ep voidptr, block bool) bool {
    mut ci := ch.ci
    mut mysg := &Fiber(0)
    mysg = ylder.getcoro()

    ci.mu.mlock()
    if ci.closed != 0 {
        ci.mu.munlock()
        panic("send on closed channel")
    }

    if ci.recvq.len > 0 {
        mut sg := ci.recvq[0]
        ci.recvq.delete(0)
        ch.send_direct0(ci.elemsize, sg, ci.mu, ep)
        return true
    }

    if ci.len < ci.cap {
        // 只是主线程sleep中断太多，过早退出进程，然而在退出时gcdeinit了
        if C.GC_is_init_called() == 0 {
            C.printf("some err %d %d\n", mysg.grid, mysg.mcid)
            C.abort()
        }
        // println("sndbuf $ci.len, ci.cap")
        // 导致 GC_init()多次调用，然后崩溃
        // mlog.info(@FILE, @LINE, "sndbuf", ci.len, ci.cap) ///
        // C.printf("sndbuf %d %d\n", ci.len, ci.cap) // OK
        // ci.elems.set(ci.sendx, ep) // private
        // C.array_set(&ci.elems, ci.sendx, ep)
        offptr := voidptr(size_t(ci.elems)+size_t(ci.sendx*ci.elemsize))
        C.memcpy(offptr, ep, ci.elemsize)
        ci.sendx++
        if ci.sendx == ci.cap {
            ci.sendx = 0
        }
        ci.len ++
        ci.mu.munlock()
        return true
    }

    if !block{
        ci.mu.munlock()
        return false
    }

    // blocking part
    mysg.elem = ep
    mysg.channel = ci
    ci.sendq << mysg

    ci.mu.munlock()
    //mlog.info(@FILE, @LINE, "sndblk", ci.len, ci.cap)
    ylder.yield(0, iohook.YIELD_TYPE_CHAN_SEND)

    mysg.param = vnil
    mysg.channel = vnil
    return true
}

// just like golang send
fn (ch Chan1) send_direct0(elemsize int, sgx &Fiber, mu &futex.Mutex, ep voidptr) {
    mut sg := sgx
    if sg.elem != vnil {
        ch.send_direct1(elemsize, sgx, ep)
        sg.elem = vnil
        sg.fromgr = vnil
    }
    mu.munlock()
    sg.param = sg
    rsmer.resume_one(sg, iohook.YIELD_TYPE_CHAN_RECV, sg.grid, sg.mcid)
}
fn (ch Chan1) send_direct1(elemsize int, sgx &Fiber, src voidptr) {
    C.memcpy(sgx.elem, src, elemsize)
}

[inline]
pub fn (ch Chan1) send<T>(v T) {
    assert int(sizeof(v)) == ch.ci.elemsize
    ch.send0(voidptr(&v), true)
}
pub fn (ch Chan1) sendnb<T>(v T) bool {
    assert int(sizeof(v)) == ch.ci.elemsize
    ch.send0(voidptr(&v), false)
    return false
}

// 没有编译器帮助，这个接收语法太难用了

pub fn (ch Chan1) recv0(ep voidptr, block bool)  {
    mut ci := ch.ci
    // two return value
    mut selected := false
    mut received := false

    if ci == vnil {
        if !block {
            return
        }
        mlog.info(@FILE, @LINE, "park forever")
        // park
        ylder.yield(0, iohook.YIELD_TYPE_CHAN_RECV_CLOSED)
        panic("unreachable")
    }

    // mlog.info(@FILE, @LINE, "recv lock")
    ci.mu.mlock()

    if ci.closed != 0 && ci.len == 0 {
        mlog.info(@FILE, @LINE, "recv unlock, closed")
        ci.mu.munlock()
        if ep != vnil {
            // typedmemclr()
        }
        selected = true
        return
    }

    if ci.sendq.len > 0 {
        // mlog.info(@FILE, @LINE, "recv sendq>0, direct")
        mut sg := ci.sendq[0]
        ci.sendq.delete(0)
        ch.recv_direct0(sg, ep, ci.mu)
        selected = true
        received = true
        return
    }

    if ci.len > 0 {
        // mlog.info(@FILE, @LINE, "rcvbuf len>0, read", ci.len)
        offptr := voidptr(size_t(ci.elems)+size_t(ci.recvx*ci.elemsize))
        qp := offptr
        // qp := C.array_get(ci.elems, ci.recvx)
        if ep != vnil {
            C.memcpy(ep, qp, ci.elemsize)
        }
        C.memset(qp, 0, ci.elemsize)
        ci.recvx++
        if ci.recvx == ci.cap {
            ci.recvx = 0
        }
        ci.len--
        ci.mu.munlock()
        selected = true
        received = true
        return
    }

    if !block {
        mlog.info(@FILE, @LINE, "need block, return")
        ci.mu.munlock()
        return
    }

    mut mysg := &Fiber(0)
    mysg = ylder.getcoro()
    mysg.elem = ep
    mysg.channel = ci
    ci.recvq << mysg

    // mlog.info(@FILE, @LINE, "need park", mysg.grid, mysg.mcid)
    ci.mu.munlock()
    ylder.yield(0, iohook.YIELD_TYPE_CHAN_RECV)
    // mlog.info(@FILE, @LINE, "recv park waked", mysg.grid, mysg.mcid)

    mysg.elem = vnil
    mysg.channel = vnil

    mut closed := false // gp.param == vnil
    closed = mysg.param == vnil
    selected = true
    received = !closed
    return
}
pub fn (ch Chan1) recv_direct0(sgx &Fiber, ep voidptr, mu &futex.Mutex, ) {
    mut ci := ch.ci
    mut sg := sgx

    if ci.cap == 0 {
        if ep != vnil {
            // recv_direct1
            C.memcpy(ep, sg.elem, ci.elemsize)
        }
    }else{
        offptr := voidptr(size_t(ci.elems)+size_t(ci.recvx*ci.elemsize))
        qp := offptr
        // qp := C.array_get(ci.elems, ci.recvx)
        if ep != vnil {
            C.memcpy(ep, qp, ci.elemsize)
        }
        C.memcpy(qp, sg.elem, ci.elemsize) // ????
        ci.recvx ++
        if ci.recvx == ci.cap {
            ci.recvx = 0
        }
        ci.sendx =  ci.recvx
    }

    sg.elem = vnil
    mu.munlock()
    sg.param = sg
    rsmer.resume_one(sg, iohook.YIELD_TYPE_CHAN_RECV, sg.grid, sg.mcid)
}


// not safe
// usage: ret := int(0); ch.recv(&ret)
[inline]
pub fn (ch Chan1) recv1(ret voidptr) {
    ch.recv0(ret, true)
}
pub fn (ch Chan1) recvnb1(ret voidptr) {
}

// safe
// usage: ret = ch.recv2(ret)
pub fn (ch Chan1) recv2<T>(v T) T {
    return v
}
pub fn (ch Chan1) recvnb2<T>(v T) T {
}

// safe
// usage: ret = ch.recv3<int>()
pub fn (ch Chan1) recv3<T>() T {
    v := T{}
    return v
}
pub fn (ch Chan1) recvnb3<T>() T {
    v := T{}
    return v
}

pub fn (ch Chan1) close() {
    
}

// this works
fn (ch Chan1) sendval<T>(v T) {
    println(v)
}
// this works
fn (ch Chan1) recvval<T>(v T) T {
    v2 := T{}
    println(v)
    return v2
}
// this works, ret := ch.recvval2<int>()
fn (ch Chan1) recvval2<T>() T {
    v2 := T{}
    println(v2)
    return v2
}


////////
/*
struct Chan2<T> {
    pub mut:
    x []T
}
fn new2<T>() Chan2<T> {
    mut ch := Chan2<T>{}
    return ch
}
*/
